// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
 * @fileoverview Generates glsl code from an ast generated by glsl.pegjs.
 * @author rowillia@google.com (Roy Williams)
 */

goog.provide('glslunit.Generator');

goog.require('glslunit.ASTVisitor');



/**
 * Creates a glsl source code generater from an ast.
 * @param {string=} opt_newline_str The character to use when printing newlines.
 *     This is useful for printing to JavaScript code vs. a normal shader file.
 * @param {boolean=} opt_debug Whether or not to generate more human readable
 *     code.
 * @constructor
 * @extends {glslunit.ASTVisitor}
 * @export
 */
glslunit.Generator = function(opt_newline_str, opt_debug) {
  goog.base(this);

  /**
   * The character to use when printing newlines.
   * @type {string}
   * @private
   * @const
   */
  this.newline_str_ = opt_newline_str || '\n';

  /**
   * Whether or not to generate more human readable code.
   * @type {boolean}
   * @private
   * @const
   */
  this.debug_ = !!opt_debug;

  /**
   * The current source code.
   * @type {string}
   * @private
   */
  this.sourceCode_ = '';

  /**
   * If we're outputting debug code, the current indentation.
   * @type {number}
   * @private
   */
   this.currentIndent_ = 0;
};
goog.inherits(glslunit.Generator, glslunit.ASTVisitor);


/**
 * The string to use when indenting a new line.
 * @type {string}
 * @private
 * @const
 */
glslunit.Generator.INDENT_STR_ = '  ';


/**
 * Possible evaluation orders of operations.
 * @enum {number}
 * @private
 */
glslunit.Generator.EvaluationOrder_ = {
  LTR: 0,
  RTL: 1,
  NA: 2
};

/**
 * @param {number} precedence The precedence for this operation.
 * @param {glslunit.Generator.EvaluationOrder_=} evaluationOrder The order which
 *     the operations evaluated in for this operation.
 * @param {boolean=} associative Whether or not this operation is associative.
 * @param {number=} maxOperands The maximum number of operands this operator
 *     can have.
 * @private
 * @constructor
 */
glslunit.Generator.OperationEvaluation_ = function(precedence,
                                                   evaluationOrder,
                                                   associative,
                                                   maxOperands) {
  /**
   * The precedence for this operation.  Operations with lower values should
   * be executed first and thus have higher precedence.
   * @type {number}
   */
  this.precedence = precedence;

  /**
   * What order are the operations evaluated in for this operation.
   * @type {glslunit.Generator.EvaluationOrder_}
   */
  this.evaluationOrder = evaluationOrder ||
      glslunit.Generator.EvaluationOrder_.LTR;

  /**
   * Whether or not this operation is associative.
   * @type {boolean}
   */
  this.associative = !!associative;

  /**
   * The maximum number of operands this operation can have.
   * @type {number}
   */
  this.maxOperands = maxOperands || 2;
};


/**
 * The precedence of all of the operators in GLSL.  Operators with lower
 * values should be executed first and thus have a higher precedence.
 * @const
 * @private
 */
glslunit.Generator.ORDER_OF_OPERATIONS_ = {
  'function_call': new glslunit.Generator.OperationEvaluation_(0,
      glslunit.Generator.EvaluationOrder_.NA),
  'identifier': new glslunit.Generator.OperationEvaluation_(0,
      glslunit.Generator.EvaluationOrder_.NA),
  'float': new glslunit.Generator.OperationEvaluation_(0,
      glslunit.Generator.EvaluationOrder_.NA),
  'int': new glslunit.Generator.OperationEvaluation_(0,
      glslunit.Generator.EvaluationOrder_.NA),
  'bool': new glslunit.Generator.OperationEvaluation_(0,
      glslunit.Generator.EvaluationOrder_.NA),
  'postfix': new glslunit.Generator.OperationEvaluation_(1,
      glslunit.Generator.EvaluationOrder_.LTR, false, 1),
  'unary': new glslunit.Generator.OperationEvaluation_(2,
      glslunit.Generator.EvaluationOrder_.RTL, false, 1),
  '*': new glslunit.Generator.OperationEvaluation_(3,
      glslunit.Generator.EvaluationOrder_.LTR, true),
  '/': new glslunit.Generator.OperationEvaluation_(3),
  '%': new glslunit.Generator.OperationEvaluation_(3),
  '+': new glslunit.Generator.OperationEvaluation_(4,
      glslunit.Generator.EvaluationOrder_.LTR, true),
  '-': new glslunit.Generator.OperationEvaluation_(4),
  '<<': new glslunit.Generator.OperationEvaluation_(5),
  '>>': new glslunit.Generator.OperationEvaluation_(5),
  '<': new glslunit.Generator.OperationEvaluation_(6),
  '>': new glslunit.Generator.OperationEvaluation_(6),
  '<=': new glslunit.Generator.OperationEvaluation_(6),
  '>=': new glslunit.Generator.OperationEvaluation_(6),
  '==': new glslunit.Generator.OperationEvaluation_(7),
  '!=': new glslunit.Generator.OperationEvaluation_(7),
  '&': new glslunit.Generator.OperationEvaluation_(8,
      glslunit.Generator.EvaluationOrder_.LTR, true),
  '^': new glslunit.Generator.OperationEvaluation_(9,
      glslunit.Generator.EvaluationOrder_.LTR, true),
  '|': new glslunit.Generator.OperationEvaluation_(10,
      glslunit.Generator.EvaluationOrder_.LTR, true),
  '&&': new glslunit.Generator.OperationEvaluation_(11,
      glslunit.Generator.EvaluationOrder_.LTR, true),
  '^^': new glslunit.Generator.OperationEvaluation_(12,
      glslunit.Generator.EvaluationOrder_.LTR, true),
  '||': new glslunit.Generator.OperationEvaluation_(13,
      glslunit.Generator.EvaluationOrder_.LTR, true),
  'ternary': new glslunit.Generator.OperationEvaluation_(14,
      glslunit.Generator.EvaluationOrder_.RTL, false, 3),
  '=': new glslunit.Generator.OperationEvaluation_(15),
  '-=': new glslunit.Generator.OperationEvaluation_(15),
  '+=': new glslunit.Generator.OperationEvaluation_(15),
  '*=': new glslunit.Generator.OperationEvaluation_(15),
  '/=': new glslunit.Generator.OperationEvaluation_(15),
  '%=': new glslunit.Generator.OperationEvaluation_(15),
  '<<=': new glslunit.Generator.OperationEvaluation_(15),
  '>>=': new glslunit.Generator.OperationEvaluation_(15),
  '&=': new glslunit.Generator.OperationEvaluation_(15),
  '^=': new glslunit.Generator.OperationEvaluation_(15),
  '|=': new glslunit.Generator.OperationEvaluation_(15)
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @return {glslunit.Generator.OperationEvaluation_} The precedence of node.
 * @private
 */
glslunit.Generator.getNodePrecedence_ = function(node) {
  return glslunit.Generator.ORDER_OF_OPERATIONS_[
    glslunit.Generator.getNodeOperator_(node)];
};


/**
 * Returns a string representing the operator for a given node.
 * @param {!Object} node The node of the AST having its source code generated.
 * @return {string} The node's operator or type.
 * @private
 */
glslunit.Generator.getNodeOperator_ = function(node) {
  if (node.type == 'binary') {
    return node.operator.operator;
  } else {
    return node.type;
  }
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @param {string=} opt_newline_str The character to use when printing newlines.
 *     This is useful for printing to JavaScript code vs. a normal shader file.
 * @param {boolean=} opt_debug Whether or not to generate more human readable
 *     code.
 * @return {string} The source code for this node.
 */
glslunit.Generator.getSourceCode = function(node, opt_newline_str, opt_debug) {
  var generator = new glslunit.Generator(opt_newline_str, opt_debug);
  generator.visitNode(node);
  return generator.sourceCode_;
};


/**
 * Checks to see if value is defined, and if it is returns pre + value + post.
 * @param {string} pre If value isn't null, the string to add before value.
 * @param {string?} value The value to check to see if it's null and
 *     return.
 * @param {string} post If value isn't null, the string to add after value.
 * @return {string} The source code.
 */
glslunit.Generator.prototype.checkAndAdd = function(pre, value, post) {
  if (value) {
    return pre + value + post;
  }
  return '';
};


/**
 * @param {Array.<Object>} nodes The nodes to print to sourceCode.
 * @param {string} joinStr The string to print between each node we print.
 * @private
 */
glslunit.Generator.prototype.addNodesWithJoin_ = function(nodes, joinStr) {
  for (var i = 0; i < nodes.length; i++) {
    if (i != 0) {
      this.sourceCode_ += joinStr;
    }
    this.visitNode(nodes[i]);
  }
};


/**
 * Print a newline and indent the next line of we're in debug mode.
 * @private
 */
glslunit.Generator.prototype.maybePrintNewline_ = function() {
  if (this.debug_) {
    this.sourceCode_ +=
        this.newline_str_ + (new Array(this.currentIndent_ + 1)).
                            join(glslunit.Generator.INDENT_STR_);
  }
};


/**
 * If we're in debug mode, chop off the last indent
 * @private
 */
glslunit.Generator.prototype.maybeUnindent_ = function() {
  if (this.debug_ &&
      this.sourceCode_.slice(-1 * glslunit.Generator.INDENT_STR_.length) ==
        glslunit.Generator.INDENT_STR_) {
    // If we're outputting in debug mode, de-indent the last brace to be in the
    // correct spot.
    this.sourceCode_ =
        this.sourceCode_.slice(0, -1 * glslunit.Generator.INDENT_STR_.length);
  }
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitStructDefinition = function(node) {
  this.sourceCode_ += this.checkAndAdd('', node.qualifier, ' ');
  this.sourceCode_ += 'struct';
  this.sourceCode_ += this.checkAndAdd(' ', node.name, '');
  this.sourceCode_ += '{';
  this.currentIndent_++;
  this.addNodesWithJoin_(node.members, '');
  this.currentIndent_--;
  this.maybeUnindent_();
  this.sourceCode_ += '}';
  if (node.declarators) {
    this.addNodesWithJoin_(node.declarators, ',');
  }
  this.sourceCode_ += ';';
  this.maybePrintNewline_();
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitScope = function(node) {
  this.sourceCode_ += '{';
  this.currentIndent_++;
  this.maybePrintNewline_();
  this.addNodesWithJoin_(node.statements, '');
  this.currentIndent_--;
  this.maybeUnindent_();
  this.sourceCode_ += '}';
  this.maybePrintNewline_();
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitPrecision = function(node) {
  this.sourceCode_ += 'precision ' + node.precision + ' ' +
                       node.typeName + ';';
  this.maybePrintNewline_();
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitInvariant = function(node) {
 this.sourceCode_ += 'invariant ';
 this.addNodesWithJoin_(node.identifiers, ',');
 this.sourceCode_ += ';';
 this.maybePrintNewline_();
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitParameter = function(node) {
  this.sourceCode_ += this.checkAndAdd('', node.typeQualifier, ' ');
  this.sourceCode_ += this.checkAndAdd('', node.parameterQualifier, ' ');
  this.sourceCode_ += this.checkAndAdd('', node.precision, ' ');
  this.sourceCode_ += node.type_name + ' ' + node.name;
  if (node.arraySize) {
    this.sourceCode_ += '[';
    this.visitNode(node.arraySize);
    this.sourceCode_ += ']';
  }
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @private
 */
glslunit.Generator.prototype.generateFunctionPrototype_ = function(node) {
  this.visitNode(node.returnType);
  this.sourceCode_ += ' ' + node.name + '(';
  this.addNodesWithJoin_(node.parameters, ',');
  this.sourceCode_ += ')';
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitFunctionPrototype = function(node) {
  this.generateFunctionPrototype_(node);
  this.sourceCode_ += ';';
  this.maybePrintNewline_();
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitFunctionDeclaration = function(node) {
  this.generateFunctionPrototype_(node);
  this.visitNode(node.body);
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitPreprocessor = function(node) {
  var lastChars = this.sourceCode_.slice(-1 * this.newline_str_.length);
  if (lastChars && lastChars != this.newline_str_) {
    this.sourceCode_ += this.newline_str_;
  }
  this.sourceCode_ += node.directive;
  if (node.directive == '#define') {
    this.sourceCode_ += ' ' + node.identifier;
    if (node.parameters) {
      this.sourceCode_ += '(';
      this.addNodesWithJoin_(node.parameters, ',');
      this.sourceCode_ += ')';
    }
    this.sourceCode_ += this.checkAndAdd(' ', node.token_string, '');
  } else {
    this.sourceCode_ += this.checkAndAdd(' ', node.value, '');
  }
  this.sourceCode_ += this.newline_str_;
  if (node.guarded_statements) {
    this.addNodesWithJoin_(node.guarded_statements, '');
    if (node.elseBody) {
      this.visitNode(node.elseBody);
    }
    if (node.directive.slice(0, 3) == '#if') {
      lastChars = this.sourceCode_.slice(-1 * this.newline_str_.length);
      if (lastChars != this.newline_str_) {
        this.sourceCode_ += this.newline_str_;
      }
      this.sourceCode_ += '#endif' + this.newline_str_;
    }
  }
};


/**
 * Generates the while statement for a do or while node.
 * @param {!Object} node The node of the AST having its source code generated.
 * @private
 */
glslunit.Generator.prototype.generateWhile_ = function(node) {
  this.sourceCode_ += 'while(';
  this.visitNode(node.condition);
  this.sourceCode_ += ')';
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitDoStatement = function(node) {
  this.sourceCode_ += 'do';
  if (node.body.type != 'scope') {
    this.sourceCode_ += ' ';
  }
  this.visitNode(node.body);
  this.generateWhile_(node);
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitWhileStatement = function(node) {
  this.generateWhile_(node);
  this.visitNode(node.body);
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitForStatement = function(node) {
  this.sourceCode_ += 'for(';
  // We don't need to add a semicolon after the initializer because it will
  // always be parsed as an expression_statement or a declarator, both of
  // which require a trailing semi-colon.
  this.visitNode(node.initializer);
  this.visitNode(node.condition);
  this.sourceCode_ += ';';
  this.visitNode(node.increment);
  this.sourceCode_ += ')';
  this.visitNode(node.body);
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitIfStatement = function(node) {
  this.sourceCode_ += 'if(';
  this.visitNode(node.condition);
  this.sourceCode_ += ')';
  this.visitNode(node.body);
  if (node.elseBody) {
    this.sourceCode_ += 'else';
    if (node.elseBody.type != 'scope') {
      this.sourceCode_ += ' ';
    }
    this.visitNode(node.elseBody);
  }
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitDeclaratorItem = function(node) {
  this.visitNode(node.name);
  if (node.isArray) {
    this.sourceCode_ += '[';
    if (node.arraySize) {
      this.visitNode(node.arraySize);
    }
    this.sourceCode_ += ']';
  }
  if (node.initializer) {
    this.sourceCode_ += '=';
    this.visitNode(node.initializer);
  }
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitDeclarator = function(node) {
  this.visitNode(node.typeAttribute);
  this.sourceCode_ += ' ';
  this.addNodesWithJoin_(node.declarators, ',');
  this.sourceCode_ += ';';
  this.maybePrintNewline_();
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitType = function(node) {
  this.sourceCode_ += this.checkAndAdd('', node.qualifier, ' ');
  this.sourceCode_ += this.checkAndAdd('', node.precision, ' ');
  this.sourceCode_ += node.name;
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitExpression = function(node) {
  this.visitNode(node.expression);
  this.sourceCode_ += ';';
  this.maybePrintNewline_();
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitJump = function(node) {
  this.sourceCode_ += node.type;
  if (node.value) {
    this.sourceCode_ += ' ';
    this.visitNode(node.value);
  }
  this.sourceCode_ += ';';
  this.maybePrintNewline_();
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitReturn =
  glslunit.Generator.prototype.visitJump;


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitBreak =
  glslunit.Generator.prototype.visitJump;


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitDiscard =
  glslunit.Generator.prototype.visitJump;


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitContinue =
  glslunit.Generator.prototype.visitJump;


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitUnary = function(node) {
  // In cases like "10- -1, we need to add the space between the '10-' and the
  // '-1' to prevent confusion.  Otherwise, it gets interpreted as
  // (10--)1, which won't compile.
  if (this.sourceCode_.slice(-1) == node.operator.operator) {
    this.sourceCode_ += ' ';
  }
  this.visitNode(node.operator);
  this.maybeParenthizeNode_(node.expression, node, 0);
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitPostfix = function(node) {
  this.maybeParenthizeNode_(node.expression, node, 0);
  this.visitNode(node.operator);
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitOperator = function(node) {
  this.sourceCode_ += node.operator;
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitFieldSelector = function(node) {
  this.sourceCode_ += '.' + node.selection;
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitAccessor = function(node) {
  this.sourceCode_ += '[';
  this.visitNode(node.index);
  this.sourceCode_ += ']';
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitFunctionCall = function(node) {
  this.sourceCode_ += node.function_name + '(';
  this.addNodesWithJoin_(node.parameters, ',');
  this.sourceCode_ += ')';
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitIdentifier = function(node) {
  this.sourceCode_ += node.name;
};


/**
 * Formats a number as a GLSL float.
 * @param {number} value The number to format as a GLSL float.
 * @return {string} The float formatted as a GLSL float.
 */
glslunit.Generator.formatFloat = function(value) {
  if (value == 0) {
    return '0.';
  }
  var lowerAndStrip = function(x) {
    // Strip off any +'s and any leading 0's
    return x.toLowerCase().replace(/^0*|\+/g, '').
        // Older versions of Firefox's implementation of toExponential will
        // leave trailing 0's after the mantissa.  So,
        // (100).toExponential() -> '1.0000e2'.
        // The below regex removes these extra 0s.
        replace(/(?:(\.[1-9]+)|\.)0*e/g, '$1e');
  }
  var floatStr = lowerAndStrip('' + value);
  var expStr = lowerAndStrip(value.toExponential());
  if (floatStr.indexOf('.') == -1 && floatStr.indexOf('e') == -1) {
    floatStr += '.';
  }
  return floatStr.length <= expStr.length ? floatStr : expStr;
};



/**
 * Formats a number as a GLSL int.
 * @param {number} value The number to format as a GLSL int.
 * @return {string} The float formatted as a GLSL int.
 */
glslunit.Generator.formatInt = function(value) {
  var decStr = value.toString(10);
  var hexStr = (value < 0 ? '-' : '') +
      '0x' + Math.abs(value).toString(16).toLowerCase();
  return decStr.length <= hexStr.length ? decStr : hexStr;
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitFloat = function(node) {
  this.sourceCode_ += glslunit.Generator.formatFloat(node.value);
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitValue = function(node) {
  this.sourceCode_ += node.value;
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitInt = function(node) {
  this.sourceCode_ += glslunit.Generator.formatInt(node.value);
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitBool =
  glslunit.Generator.prototype.visitValue;


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitBinary = function(node) {
  this.maybeParenthizeNode_(node.left, node, 0);
  this.visitNode(node.operator);
  this.maybeParenthizeNode_(node.right, node, 1);
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @protected
 * @export
 */
glslunit.Generator.prototype.visitTernary = function(node) {
  this.maybeParenthizeNode_(node.condition, node, 0);
  this.sourceCode_ += '?';
  this.maybeParenthizeNode_(node.is_true, node, 1);
  this.sourceCode_ += ':';
  this.maybeParenthizeNode_(node.is_false, node, 2);
};


/**
 * @param {!Object} node The node of the AST having its source code generated.
 * @param {!Object} parentNode The parent of node.
 * @param {number} operandIndex The index of node's operand.
 * @private
 */
glslunit.Generator.prototype.maybeParenthizeNode_ = function(node,
                                                             parentNode,
                                                             operandIndex) {
  var nodePrecedence = glslunit.Generator.getNodePrecedence_(node);
  var parentPrecedence = glslunit.Generator.getNodePrecedence_(parentNode);
  var addParen = false;
  if (nodePrecedence.precedence > parentPrecedence.precedence) {
    addParen = true;
  } else if (nodePrecedence.precedence == parentPrecedence.precedence) {
    var nodeOperator = glslunit.Generator.getNodeOperator_(node);
    var parentOperator = glslunit.Generator.getNodeOperator_(parentNode);
    var nodeIsFirstOperation =
        (parentPrecedence.evaluationOrder ==
         glslunit.Generator.EvaluationOrder_.LTR &&
           operandIndex == 0) ||
        (parentPrecedence.evaluationOrder ==
         glslunit.Generator.EvaluationOrder_.RTL &&
           operandIndex == parentPrecedence.maxOperands - 1);
    // If node would be evaluated first among the the children of parent to be
    // executed anyway just due to the evaluation order, there is no need to
    // parenthize node.  There is a bug here where
    // a ? b ? c : d : e will wind up being parenthized
    // a ? (b ? c : d) : e when it doesn't need to be, but that's so rare and it
    // would overly complicate this already complicated code.
    if (nodeIsFirstOperation) {
      addParen = false;
    } else if (nodeOperator == parentOperator && nodePrecedence.associative) {
      // If the child node is the same operator as the parent node but won't be
      // executed first, only skip the paren if their operator is associative.
      // For example, a * (b * c) will generate a * b * c, because since
      // multiplication is associative, the order in which the operations are
      // executed doesn't matter.
      addParen = false;
    } else {
      addParen = true;
    }
  } else {
    addParen = false;
  }
  if (addParen) {
    this.sourceCode_ += '(';
  }
  this.visitNode(node);
  if (addParen) {
    this.sourceCode_ += ')';
  }
};
